package com.tool.hooker;

import android.app.Application;
import android.content.Context;

import com.hooker.Dig;
import com.hooker.common.StringUtil;
import com.hooker.settings.HLog;
import com.hooker.settings.Setting;
import com.hooker.settings.SettingHelper;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;


public class HookEntry implements IXposedHookLoadPackage {

    @Override
    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {

        final Set<String> pgSets = new HashSet<>();
        loadPackageParam.setObjectExtra("package_set", pgSets);

        HLog.o("handle loaded package = " + loadPackageParam.packageName);
        try {
            XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    String pg_process = loadPackageParam.packageName + ":" + loadPackageParam.processName;

                    if (pgSets.contains(pg_process)) {
                        return;
                    }

                    try {
                        Context context = ((Context) param.args[0]);

                        if (Setting.isDebug(context)) {
                            HLog.o("init model " + pg_process);
                        }

                        Set<String> hookApps = Setting.getHookApps(context);

                        if (hookApps.size() == 0) {
                            HLog.o("tool.hook apps is empty");
                        }

                        if (hookApps.contains(loadPackageParam.packageName)) {
                            HLog.o("tool.hook " + loadPackageParam.packageName);

                            loadPlugins(context, Setting.getHookPlugin(context), loadPackageParam);
                        }
                        pgSets.add(pg_process);

                    } catch (Throwable e) {
                        HLog.err(e);
                    }
                }
            });
        } catch (Throwable e) {
            XposedBridge.log(e);
        }
    }


    private void loadPlugins(Context c, Set<String> plugins, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        for (String plugin : plugins) {
            if ("null".equals(plugin) || plugin == null) {
                continue;
            }

            if (Setting.isDebug(c)) {
                HLog.o("begin load plugin " + plugin);
            }

            Object hookPackage = loadPlugin(c, plugin, loadPackageParam);

            if (hookPackage == null) {
                return;
            }

            Method hook = null;
            Class cls = hookPackage.getClass();
            while (hook == null && cls != null) {
                try {
                    hook = cls.getDeclaredMethod("hook", Context.class, XC_LoadPackage.LoadPackageParam.class);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (SecurityException e) {
                    e.printStackTrace();
                }
                cls = cls.getSuperclass();
            }

            if (hook != null) {
                hook.setAccessible(true);
                hook.invoke(hookPackage, c, loadPackageParam);
            }
        }
    }

    private Object loadPlugin(Context c, String path, XC_LoadPackage.LoadPackageParam loadPackageParam) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        File h_plugin = c.getDir("h_plugins_" + Dig.md5(path), 0);

        PathClassLoader pathClassLoader = new PathClassLoader(path, getClass().getClassLoader().getParent());

        Method findClass = ClassLoader.class.getDeclaredMethod("findClass", String.class);
        findClass.setAccessible(true);

        String pluginClass = Setting.getPluginClass(c);

        if (pluginClass == null) {
            pluginClass = "com.tool.hooker.plugins.PluginEntry";
        }

        try {
//            Class<?> cls = pathClassLoader.loadClass(pluginClass);
            Class<?> cls = (Class<?>) findClass.invoke(pathClassLoader, pluginClass);
            Constructor constructor = cls.getDeclaredConstructor(Context.class, XC_LoadPackage.LoadPackageParam.class);
            constructor.setAccessible(true);
            Object plugin = constructor.newInstance(c, loadPackageParam);

            if (Setting.isDebug(c)) {
                HLog.o("load plugin " + plugin.getClass().getClassLoader() + " success in " + h_plugin);
            }

            return plugin;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}
